package java_thinking.unit_14._14_9;

import java.lang.reflect.Method;

/**
 * @Description :  这里有点不懂！！！可能需要一些实际的案例才能明白
 * @author: cww
 * @DateTime: 2019-03-31 20:44
 */

interface A {
    void f();
}

class B implements A {
    @Override
    public void f() {};
    public void g() {};
}
/**
 * @author asd730520@live.com
 */
public class InterfaceViolation {
    public static void main(String[] args) {
        A a = new B();
        a.f();
//        a.g(); 编译异常 必须要强转为((B)a).g()
        //
        /**
         * 通过使用RTTI，我们发现a是被当做B实现的，通过转型为B，我们可以调用不在A中的方法。
         * 但是也许接口开发者并不想让客户端程序员这么做，因为这会导致接口开发者的代码与使用者的
         * 代码过度耦合；要解决这个问题可见InnerA
         */
        ((B)a).g();
        System.out.println(a.getClass().getName());
        if (a instanceof B) {
            B b = (B)a;
            b.g();
        }
    }
}

/**
 * 对实现使用包访问权限，这样在包外部的客户端就不能看到它了
 */
class InnerA {
    private static class C implements A {
        @Override
        public void f() { System.out.println("public C.f()"); }
        public void g() { System.out.println("public C.g()"); }
        void u() { System.out.println("package C.u()"); }
        protected void v() { System.out.println("protected C.v()"); }
        private void w() { System.out.println("private C.w()"); }
    }

    /**
     * 在这个包中唯一的public的部分，在调用时将产生A接口的类型的对象。
     * 这里有趣之处在于：即使你从makeA()返回的是C类型，在包的外部人就不能使用A之外的任何方法，
     * 因为你不能在包外部命名C
     * @return
     */
    public static A makeA() { return new C(); }
}

class HiddenImplementation {
    public static void main(String[] args) throws Exception {
        A a = InnerA.makeA();
        a.f();
//        a.g() 不被允许
//        InnerA.C c = (InnerA.C)a
        // Reflection still gets into the private class:
        callHiddenMethod(a, "g");
        callHiddenMethod(a, "u");
        callHiddenMethod(a, "v");
        callHiddenMethod(a, "w");
    }

    /**
     * 但是通过反射还是可以取到内部非公开的方法,即使使用内部类也无效
     * 可以通过反编译来对编译后的类读取方法名：javap -private C
     * @param a
     * @param methodName
     * @throws Exception
     */
    static void callHiddenMethod(Object a, String methodName) throws Exception {
        Method g = a.getClass().getDeclaredMethod(methodName);
        g.setAccessible(true);
        g.invoke(a);




        Method m2 = a.getClass().getDeclaredMethod(methodName);
        m2.setAccessible(true);
        m2.invoke(a);
    }


}